﻿using System;
using System.Diagnostics;
using System.IO;
using System.Windows.Media.Imaging;
using LFSRecord2.Misc;
using System.ComponentModel;

namespace LFSRecord2.Model.Sound
{
    public abstract class WaveStream : Stream, INotifyPropertyChanged
    {
        protected string _audioFilePath;
        protected EndianFileStream _audioStream;
        protected long _audioDataPos;
        protected long _audioLength;

        public WaveTypes AudioType { get; protected set; }
        protected WaveFormatEx _audioFormat;
        public WaveFormatEx Format
        {
            get 
            {
                if (_audioFormat == null)
                    _audioFormat = new WaveFormatEx(44100, 16, 2);
                return _audioFormat; 
            }
        }

        /// <summary>
        /// Total duration of the audio in seconds
        /// </summary>
        public double Duration { get; set; }

        BitmapSource _waveView;
        public BitmapSource WaveView
        {
            get 
            { 
                return _waveView; 
            }
            protected set
            {
                _waveView = value;
                OnPropertyChanged("WaveView");
            }
        }

        public event PropertyChangedEventHandler PropertyChanged;
        protected virtual void OnPropertyChanged(string propertyName)
        {
            PropertyChangedEventHandler handler = this.PropertyChanged;
            if (handler != null)
            {
                var e = new PropertyChangedEventArgs(propertyName);
                handler(this, e);
            }
        }

        const double TO_FLOAT_DIV = (double)Int32.MaxValue;
        public void GenerateWaveView()
        {
            int bitmapWidth = (int)(Duration * LfsRecordController.project.FPS);
            if (bitmapWidth == 0)
            {
                WaveView = null;
                return;
            }
            WaveBitmap rb = new WaveBitmap(bitmapWidth, 31);

            int numBlocks = (int)(Length / Format.nBlockAlign);
            int sampleStep = numBlocks / bitmapWidth * Format.nBlockAlign;
            sampleStep -= sampleStep % Format.nBlockAlign;

            ByteBuffer sampleData = new ByteBuffer(sampleStep, ((AudioType == WaveTypes.Aif) ? EndianTypes.Little : EndianTypes.Big));

            int a = sampleStep, s, x = 1;
            float sampleTmp;
            float[] samplePeaks = new float[2];
            byte[] sampleBytes = new byte[4];
            int sampleLen = Format.nBlockAlign / Format.nChannels;
            int byteOffset = 4 - sampleLen;
            Position = sampleStep;
            while (a < Length - sampleStep)
            {
                // Read a sample data block of size sampleStep and gather high and low peaks
                samplePeaks[0] = samplePeaks[1] = 0.0f;

                // Read a big block of data from disk. This speeds things up considerably as opposed to reading byte for byte.
                _audioStream.Read(sampleData.Buf, 0, sampleStep);
                sampleData.Position = 0;

                s = a + sampleStep;
                while (sampleData.Position < sampleStep)
                {
                    for (int c = 0; c < Format.nChannels; c++)
                    {
                        // Read channel sample from cache
                        sampleData.ReadBytes(sampleBytes, byteOffset, sampleLen);
                        if (sampleLen == 1)
                            sampleTmp = (float)(((int)sampleBytes[3] - 0x7F) / 127.0f);
                        else
                            sampleTmp = (float)(BitConverter.ToInt32(sampleBytes, 0) / TO_FLOAT_DIV);

                        // Min and max value
                        if (sampleTmp > 0)
                        {
                            if (sampleTmp > samplePeaks[0])
                                samplePeaks[0] = sampleTmp;
                        }
                        else
                        {
                            if (sampleTmp < samplePeaks[1])
                                samplePeaks[1] = sampleTmp;
                        }
                    }
                    // Increase data pointer while skipping 15 samples to speed things up.
                    sampleData.Position += Format.nBlockAlign * 10;
                }
                a += sampleStep;

                // Draw from low to high value on this x offset
                rb.setVLine(
                    x, 
                    (int)Math.Round(samplePeaks[1] * 15) + 15,
                    (int)Math.Round(samplePeaks[0] * 15) + 15);

                x++;
            }

            WaveView = rb.CreateBitmapSource();
            WaveView.Freeze();
        }

        public new void Dispose()
        {
            if (_audioStream != null)
                _audioStream.Close();
            base.Dispose();
            GC.SuppressFinalize(this);
        }

        public override bool CanRead
        {
            get { return true; }
        }
        public override bool CanSeek
        {
            get { return true; }
        }
        public override bool CanWrite
        {
            get { return false; }
        }
        public override long Length
        {
            get { return _audioLength; }
        }
        public override long Position
        {
            get { return _audioStream.Position - _audioDataPos; }
            set { Seek(value, SeekOrigin.Begin); }
        }
        public override void Close()
        {
            Debug.WriteLine("Closing stream");
            //Dispose();
        }
        public override void Flush()
        {
        }
        public override void SetLength(long value)
        {
            throw new InvalidOperationException();
        }
        public override long Seek(long offset, SeekOrigin origin)
        {
            switch (origin)
            {
                case SeekOrigin.Begin:
                    _audioStream.Position = offset + _audioDataPos;
                    break;
                case SeekOrigin.Current:
                    _audioStream.Seek(offset, SeekOrigin.Current);
                    break;
                case SeekOrigin.End:
                    _audioStream.Position = _audioDataPos + _audioLength - offset;
                    break;
            }
            return this.Position;
        }
        public override int Read(byte[] buffer, int offset, int count)
        {
            int toread = (int)Math.Min(count, _audioLength - Position);
            //if (toread < 0)
            //    return 0;
            return _audioStream.Read(buffer, offset, toread);
        }
        public void ReadBytes(byte[] array, int offset, int count)
        {
            _audioStream.ReadBytes(array, offset, count);
        }
        public override void Write(byte[] buffer, int offset, int count)
        {
            throw new InvalidOperationException();
        }
    }
}
